﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading;
using System.IO;
using System.IO.Ports;

namespace LIVITECH_ModbusDevLib
{
    public class ODLDeviceProcess
    {
        public Action<string, System.Drawing.Brush> toastInfoDel;
        public bool reqQuitRun = false;
        public string portname = "COM2";    //串口号
        public int baudrate = 19200;        //波特率
        public int slaveID = 2;             //Modbus设备 从机ID

        private const int pduTimeOut = 50;      //Modbus 一个PDU超时的时间 ms

        private int modbusErrCnt = 0;

        private const UInt16 Addr_Motor1Status = 32;      //步进电机当前状态  32位
        private const UInt16 Addr_Motor1Fault = 34;      //步进电机错误状态  32位
        private const UInt16 Addr_Motor1Pulse = 36;      //步进电机当前脉冲位置  32位
        private const UInt16 Addr_Motor1Encoder = 38;      //步进电机当前编码器值  32位

        private int _motor1Status = 0;   //步进电机状态缓冲
        private int _motor1Fault = 0;    //步进电机错误码缓冲
        private int _motor1Pulse = 0;    //步进电机当前脉冲位置
        private int _motor1Encoder = 0;    //步进电机当前编码器值

        private const UInt16 Addr_Motor1CmdVar1 = 64;      //配合步进电机命令的 变量1  32位
        private const UInt16 Addr_Motor1CmdVar2 = 66;      //配合步进电机命令的 变量2  32位
        private const UInt16 Addr_Motor1CmdVar3 = 68;      //配合步进电机命令的 变量3  32位

        //private int motor1CmdVar1 = 0;
        //private int motor1CmdVar2 = 0;
        //private int motor1CmdVar3 = 0;

        private const UInt16 Addr_Motor1Cmd = 70;   //控制步进电机的命令寄存器地址 32位
        private const UInt16 Motor1Cmd_Stop = 0x001;   //步进电机停止运行命令值
        private const UInt16 Motor1Cmd_Home = 0x002;   //步进电机寻参回零命令值
        private const UInt16 Motor1Cmd_EncAbsGo = 0x005;   //步进电机高精度绝对定位命令值
        private const UInt16 Motor1Cmd_JogP = 0x7000;   //步进电机点动正向命令值
        private const UInt16 Motor1Cmd_JogN = 0x8000;   //步进电机点动负向命令值

        private bool hasWriteCmd = false;       //是否存在需要写通信的命令
        public bool cmdMotor1Stop = false;     //停止步进电机命令
        public bool cmdMotor1Home = false;     //步进电机寻参回零命令
        public bool cmdMotor1EncAbsGo = false;     //步进电机高精度绝对定位命令
        public bool cmdMotor1JogP = false;     //步进电机正向点动命令
        public bool cmdMotor1JogN = false;     //步进电机负向点动命令

        public int encAbsGoDestination = 0; //高精度绝对定位目标位置

        public ODLDeviceProcess()
        {

        }

        private UInt16 _motor1JogTlimit = 100;  //点动最短有效时长 单位ms
        public UInt16 motor1JogTlimit
        {
            set
            {
                if(value < 10)
                {
                    _motor1JogTlimit = 10;
                }else if(value > 1000)
                {
                    _motor1JogTlimit = 1000;
                }else
                {
                    _motor1JogTlimit = value;
                }
                
            }
        }

        public string motor1StatusInfo
        {
            get
            {
                return motor1StatusToStr(_motor1Status);
            }
        }

        public string motor1FaultInfo
        {
            get
            {
                return motor1FaultToStr(_motor1Fault);
            }
        }

        public int motor1Pulse
        {
            get
            {
                return _motor1Pulse;
            }
        }

        public int motor1Encoder
        {
            get
            {
                return _motor1Encoder;
            }
        }

        private void toastInfo(string info, System.Drawing.Brush color)
        {
            if (toastInfoDel != null)
            {
                toastInfoDel(info, color);
            }
        }

        //状态值转字符串
        private string motor1StatusToStr(int status)
        {
            string res="";
            if((status & 1) != 0)
            {
                res = "电机已停止";
            }

            if((status & 2) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机回零已经完成";
            }

            if ((status & 4) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机回零中";
            }

            if ((status & 8) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机相对运动中";
            }

            if ((status & 16) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机绝对运动中";
            }
            if ((status & 32) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机编码器参考绝对运动中";
            }
            if ((status & 64) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机处于扫描运行中";
            }
            if ((status & 0x10000) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机到达负限位";
            }
            if ((status & 0x20000) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机到达正限位";
            }
            if ((status & 0x30000) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机到达软件负限位";
            }
            if ((status & 0x40000) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "电机到达软件正限位";
            }

            return res;
        }

        //错误值转字符串
        private string motor1FaultToStr(int fault)
        {
            string res = "";

            if ((fault & 1) != 0)
            {
                res = res + "运动中到达软正限位";
            }

            if ((fault & 2) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "运动中到达软负限位";
            }

            if ((fault & 4) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "运动中到达正限位";
            }

            if ((fault & 8) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "运动中到达负限位";
            }

            if ((fault & 16) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "回零失败";
            }
            if ((fault & 32) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "与编码器值超差";
            }
            if ((fault & 64) != 0)
            {
                if (!string.IsNullOrEmpty(res)) res = res + ";\n";
                res = res + "DRV8880芯片出错";
            }
            return res;
        }

        //检查外部控制命令，按一些优先级进行过滤
        private void checkOutsideCmd()
        {
            hasWriteCmd = cmdMotor1Stop ||
                cmdMotor1Home ||
                cmdMotor1EncAbsGo ||
                cmdMotor1JogP ||
                cmdMotor1JogN;

            if (cmdMotor1Stop)
            {
                cmdMotor1Home = false;
                cmdMotor1EncAbsGo = false;
                cmdMotor1JogP = false;
                cmdMotor1JogN = false;

            }
            else if (cmdMotor1JogP)
            {
                cmdMotor1Home = false;
                cmdMotor1EncAbsGo = false;
                cmdMotor1JogN = false;

            }
            else if (cmdMotor1JogN)
            {
                cmdMotor1Home = false;
                cmdMotor1EncAbsGo = false;
            }
            else if (cmdMotor1Home)
            {
                cmdMotor1EncAbsGo = false;
            }
            else if (cmdMotor1EncAbsGo)
            {

            }
        }

        //返回是否通信失败
        public bool IsCommFailed
        {
            get
            {
                return modbusErrCnt >= 2;
            }
        }

        //清零通信错误计数器
        public void ClearErrCnt()
        {
            modbusErrCnt = 0;
        }

        //从4x寄存器读回值
        private bool readRegsFromDev(ref SerialPort serialport, UInt16 addr, UInt16 len, out UInt16[] regs) //从设备 读回保持寄存器的值
        {
            regs = null;
            if (len < 1 || slaveID == 0) return true;

            for (int i = 0; i < 3; i++) //尝试执行三次通信
            {
                serialport.DiscardInBuffer();
                serialport.DiscardOutBuffer();

                //生成读 寄存器 pdu  并发送pdu
                byte[] sendpdu;
                ScanDevConsole.ModbusHelp.generateReadRegsPDU(out sendpdu, slaveID, addr, len);
                //Debug.WriteLine(BitConverter.ToString(sendpdu));
                try
                {
                    serialport.Write(sendpdu, 0, sendpdu.Length);
                }
                catch
                {
                    if (modbusErrCnt < 10)
                    {
                        toastInfo("Modbus 发送超时", Brushes.Red);
                    }
                    if (i == 2 && modbusErrCnt < 10)
                    {
                        modbusErrCnt++;
                    }
                    if (i == 2) return false;
                    continue;
                }

                //接收回复
                byte[] recvpdu = new byte[512];
                int readedCnt = 0;

                serialport.ReadTimeout = pduTimeOut * 2;   //第一个字节时间长一点
                try
                {
                    readedCnt += serialport.Read(recvpdu, readedCnt, 1);
                }
                catch
                {

                }
                serialport.ReadTimeout = pduTimeOut;
                while (true)    //继续读取剩余的字节
                {
                    int rb = 0;
                    try
                    {
                        rb = serialport.Read(recvpdu, readedCnt, recvpdu.Length - readedCnt);
                    }
                    catch
                    {
                    }

                    if (rb < 1) break;
                    else readedCnt += rb;

                    if (readedCnt >= 512) break;
                }

                if (readedCnt < 1)
                {
                    if (modbusErrCnt < 10)
                    {
                        toastInfo("Modbus 接收超时", Brushes.Red);
                    }
                    if (i == 2 && modbusErrCnt < 10)
                    {
                        modbusErrCnt++;
                    }
                    if (i == 2) return false;
                    continue;
                }

                //解码回复
                byte[] tt = recvpdu.Take(readedCnt).ToArray();
                //Debug.WriteLine(BitConverter.ToString(tt));

                bool isreadOk = ScanDevConsole.ModbusHelp.decodeReadRegsResponse(ref sendpdu, ref tt, out regs);

                if (isreadOk && regs != null && regs.Length > 0)
                {
                    if (modbusErrCnt >= 10)
                    {
                        toastInfo("Modbus 连接恢复", Brushes.Green);
                    }
                    modbusErrCnt = 0;
                    break;
                }
                else
                {
                    if (modbusErrCnt < 10)
                    {
                        toastInfo("Modbus 接收到错误的回复", Brushes.Red);
                    }
                    if (i == 2 && modbusErrCnt < 10)
                    {
                        modbusErrCnt++;
                    }
                    if (i == 2) return false;
                }

            }   //尝试执行三次通信

            return true;
        }


        //写值到 4x 寄存器
        private bool writeRegsToDev(ref SerialPort serialport, UInt16 addr, ref UInt16[] regs)    //写数据到设备的保持寄存器 成功返回true
        {
            if (regs == null) return true;
            if (regs.Length < 1) return true;

            for (int i = 0; i < 3; i++) //尝试执行三次通信
            {
                serialport.DiscardInBuffer();
                serialport.DiscardOutBuffer();

                //生成读 寄存器 pdu  并发送pdu
                byte[] sendpdu;

                ScanDevConsole.ModbusHelp.generateWriteRegsPDU(out sendpdu, slaveID, addr, ref regs);
                ScanDevConsole.ModbusHelp.calcAndAppendCRC(ref sendpdu);
                //Debug.WriteLine(BitConverter.ToString(sendpdu));
                try
                {
                    serialport.Write(sendpdu, 0, sendpdu.Length);
                }
                catch
                {
                    if (modbusErrCnt < 10)
                    {
                        toastInfo("Modbus 发送超时", Brushes.Red);
                    }
                    if (i == 2 && modbusErrCnt < 10)
                    {
                        modbusErrCnt++;
                    }
                    if (i == 2) return false;
                    continue;
                }

                if (slaveID == 0) break;    //广播时 不进行回复检查

                //接收回复
                byte[] recvpdu = new byte[512];
                int readedCnt = 0;

                serialport.ReadTimeout = pduTimeOut * 2;   //第一个字节时间长一点
                try
                {
                    readedCnt += serialport.Read(recvpdu, readedCnt, 1);
                }
                catch
                {

                }
                serialport.ReadTimeout = pduTimeOut;
                while (true)    //继续读取剩余的字节
                {
                    int rb = 0;
                    try
                    {
                        rb = serialport.Read(recvpdu, readedCnt, recvpdu.Length - readedCnt);
                    }
                    catch
                    {
                    }

                    if (rb < 1) break;
                    else readedCnt += rb;

                    if (readedCnt >= 512) break;
                }

                if (readedCnt < 1)
                {
                    if (modbusErrCnt < 10)
                    {
                        toastInfo("Modbus 接收超时", Brushes.Red);
                    }
                    if (i == 2 && modbusErrCnt < 10)
                    {
                        modbusErrCnt++;
                    }
                    if (i == 2) return false;
                    continue;
                }

                //解码回复
                byte[] tt = recvpdu.Take(readedCnt).ToArray();
                //Debug.WriteLine(BitConverter.ToString(tt));
                bool isreadOk = ScanDevConsole.ModbusHelp.isPDU_CRC_OK(ref tt);    //检查CRC是否正确
                for (int j = 0; j < 6; j++)
                {
                    isreadOk = isreadOk && (tt[j] == sendpdu[j]);
                    if (!isreadOk) break;
                }

                if (!isreadOk)
                {
                    if (modbusErrCnt < 10)
                    {
                        toastInfo("Modbus 接收到错误的回复", Brushes.Red);
                    }
                    if (i == 2 && modbusErrCnt < 10)
                    {
                        modbusErrCnt++;
                    }
                    if (i == 2) return false;
                }
                else
                {
                    if (modbusErrCnt >= 10)
                    {
                        toastInfo("Modbus 连接恢复", Brushes.Green);
                    }
                    modbusErrCnt = 0;
                    break;
                }

            }   //尝试执行三次通信
            return true;
        }

        private void doReadFromDev(ref SerialPort serialport)    //执行通信从设备读取
        {
            bool readok = false;
            //读取步进电机的状态、错误、位置信息

            UInt16[] temp = null;
            readok = readRegsFromDev(ref serialport, Addr_Motor1Status, 8, out temp);
            if (readok && temp.Length>=8)
            {
                int t1;
                int t2;
                int i;

                i = (Addr_Motor1Status - Addr_Motor1Status);
                t1 = temp[i];
                t2 = temp[i + 1];
                t1 = t2 << 16 | t1;
                _motor1Status = t1;

                i = (Addr_Motor1Fault - Addr_Motor1Status);
                t1 = temp[i];
                t2 = temp[i + 1];
                t1 = t2 << 16 | t1;
                _motor1Fault = t1;

                i = (Addr_Motor1Pulse - Addr_Motor1Status);
                t1 = temp[i];
                t2 = temp[i + 1];
                t1 = t2 << 16 | t1;
                _motor1Pulse = t1;

                i = (Addr_Motor1Encoder - Addr_Motor1Status);
                t1 = temp[i];
                t2 = temp[i + 1];
                t1 = t2 << 16 | t1;
                _motor1Encoder = t1;
            }
        }

        private void doWriteToDev(ref SerialPort serialport)      //执行写入通信到设备
        {
            UInt16[] temp = new UInt16[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
            bool writeRegsOK = false;
            int cmd = Motor1Cmd_Stop;
            int i = 0;

            if (cmdMotor1Stop)
            {
                cmd = Motor1Cmd_Stop;
                //cmdMotor1Stop = false;
            }
            else if (cmdMotor1JogP)
            {
                cmd = 0;
                cmd = cmd | (Motor1Cmd_JogP + _motor1JogTlimit);
                cmdMotor1JogP = false;
            }
            else if (cmdMotor1JogN)
            {
                cmd = 0;
                cmd = cmd | (Motor1Cmd_JogN + _motor1JogTlimit);
                cmdMotor1JogN = false;
            }
            else if (cmdMotor1Home)
            {
                cmd = Motor1Cmd_Home;
                cmdMotor1Home = false;
            }
            else if (cmdMotor1EncAbsGo)
            {
                cmd = Motor1Cmd_EncAbsGo;

                i = (Addr_Motor1CmdVar1 - Addr_Motor1CmdVar1);
                temp[i] = (UInt16)(encAbsGoDestination & 0x0FFFF);
                temp[i + 1] = (UInt16)(encAbsGoDestination >> 16);

                cmdMotor1EncAbsGo = false;
            }
            else
            {
                return;
            }

            if(cmdMotor1JogP || cmdMotor1JogN || cmdMotor1Stop)
            {
                temp = new UInt16[2] { 0, 0 };
                temp[0] = (UInt16)(cmd & 0x0FFFF);
                temp[1] = (UInt16)(cmd >> 16);
                writeRegsOK = writeRegsToDev(ref serialport, Addr_Motor1Cmd, ref temp);
            }
            else
            {
                i = (Addr_Motor1Cmd - Addr_Motor1CmdVar1);
                temp[i] = (UInt16)(cmd & 0x0FFFF);
                temp[i + 1] = (UInt16)(cmd >> 16);

                writeRegsOK = writeRegsToDev(ref serialport, Addr_Motor1CmdVar1, ref temp);
            }

            if (cmdMotor1Stop)
            {
                if (writeRegsOK) cmdMotor1Stop = false;
            }
        }

        public void ScanDevRun()        //线程运行的函数入口
        {
            //打开串口
            SerialPort serialport = new SerialPort();
            serialport.PortName = portname;
            serialport.BaudRate = baudrate;
            serialport.DataBits = 8;
            serialport.StopBits = System.IO.Ports.StopBits.One;
            serialport.Parity = System.IO.Ports.Parity.Even;
            serialport.RtsEnable = false;
            serialport.DtrEnable = false;
            serialport.WriteTimeout = 500;

            modbusErrCnt = 0;

            toastInfo(string.Format("准备打开串口 {0}, 波特率 {1} , 设备ID {2}",
                serialport.PortName, serialport.BaudRate, slaveID
                ),
                Brushes.Green);

            try
            {
                serialport.Open();
            }
            catch
            {

            }

            if (!serialport.IsOpen)
            {
                toastInfo(string.Format("打开串口 {0} 失败!", serialport.PortName), Brushes.Red);
                return;
            }
            else
            {
                toastInfo(string.Format("打开串口 {0} 成功!", serialport.PortName), Brushes.Green);
            }

            /////////////////////////////////////////////////////////////////////
            int skipReadCnt = 0;
            while (true)        //线程循环
            {
                checkOutsideCmd();

                //执行写入通信到设备
                if(hasWriteCmd) doWriteToDev(ref serialport);

                if (reqQuitRun) break;
                if (modbusErrCnt >= 10)
                {
                    System.Threading.Thread.Sleep(1000);
                }
                else
                {
                    System.Threading.Thread.Sleep(5);
                }

                //点动时 减小读取的频率 
                if(cmdMotor1JogP || cmdMotor1JogN)
                {
                    skipReadCnt++;
                    if (skipReadCnt > 4) skipReadCnt = 0;
                    if (skipReadCnt%4 != 0)
                    {
                        continue;
                    }
                }

                //执行通信从设备读取
                doReadFromDev(ref serialport);

            }

            ///////////////////////////////////////////////////////////////////

            serialport.Close();
            toastInfo(string.Format("关闭串口 {0} ", serialport.PortName), Brushes.Green);

            toastInfo("设备读写线程结束", Brushes.Black);
            reqQuitRun = false;

        }

    }
}
